<template>
  <div v-loading='loading' class="basic-layout">
    <top-bar
      :supportVehicles="supportVehicles"
      :saveLoading='saveLoading'
      :editMode="editMode"
      @editModeChange="editModeChange"
      @vehicleValidate="toggleVehicleValidatePanel"
      @clashValidate="toggleClashValidatePanel"
      @chooseValidate="toggleChooseValidatePanel"
      @simulation="goSimulation"
      @exit="exit"
      @saveRuleChain="saveRuleChain">
    </top-bar>
    <div class="mainLayout">
      <side-bar v-if="editEnable" @startDrag="addNewNode"></side-bar>
      <div class="ruleChain-container">
        <rule-chain-tools :graph="graph" v-if="graph"></rule-chain-tools>
        <div id="container"></div>
      </div>
      <node-details-panel
        :node="selectedItem.type === 'node' ? selectedItem.data : {}"
        :visible="selectedItem.type === 'node'"
        @close="clearSelected"
        @save="saveNode"
      ></node-details-panel>
      <edge-details-panel
        :edge="selectedItem.type === 'edge' ? selectedItem.data : {}"
        :visible="selectedItem.type === 'edge'"
        @close="clearSelected"
      ></edge-details-panel>
      <vehicle-validate-panel
        :visible.sync="validatePanelVisible"
        :supportVehicles="supportVehicles"
      ></vehicle-validate-panel>
      <clash-validate-panel
        :visible.sync="clashPanelVisible"
      ></clash-validate-panel>
      <choose-validate-panel
        :visible.sync="choosePanelVisible"
        :graph="graph"
      ></choose-validate-panel>
    </div>
  </div>
</template>

<script>
import { Addon } from '@antv/x6'
import MainGraph from '@/components/graph/graph.config'
import RuleChainApi from '@/api/ruleChain'
import { mergeObject, debounce } from '@/utils/helper/jsUtil'
import formatSubmitData from '@/utils/format'

import {
  validateNoEdgeNode,
  validateNoTouchNode,
  validateSurplusTouchNode,
  validateAutoTouchNode,
  validateVoiceNode,
  validateNoEndNode,
  validateEmptyNode,
  validateSupportVehicles,
  getRuleChainAllSignalsAndServices,
  EdgeRemoveBtnArgs,
  createStageGraph,
  // adapterNodeByRuleChain,
  creatParallelNode,
  initParallelNode,
  editRuleChainDataByNode,
  graphIsChange
} from './rulechain-page'

import TopBar from '../topBar/index.vue'
import SideBar from '../sideBar/index.vue'
import RuleChainTools from './ruleChain-tools.vue'
import NodeDetailsPanel from './node-details-panel.vue'
import EdgeDetailsPanel from './edge-details-panel.vue'
import VehicleValidatePanel from './vehicle-validate-panel.vue'
import ClashValidatePanel from './clash-validate-panel.vue'
import ChooseValidatePanel from './choose-validate-panel.vue'

import { nodeClashValidate } from '@/utils/validate'
import { NodeType, NodeConfig, StageType } from '@/model/node.model'
import { LabelText } from '@/model/edge.model'
import { ProductTypeSimple } from '@/model/product.model'
import { mapState, mapActions } from 'vuex'

export default {
  name: 'ruleChainPage',
  components: {
    TopBar,
    SideBar,
    RuleChainTools,
    NodeDetailsPanel,
    EdgeDetailsPanel,
    VehicleValidatePanel,
    ClashValidatePanel,
    ChooseValidatePanel
  },
  data() {
    return {
      graph: undefined,
      saveLoading: false,
      // 拖拽实例
      dnd: null,
      nextPortId: 1,
      nextNodeId: 0,
      supportVehicles: [],
      validatePanelVisible: false,
      clashPanelVisible: false,
      choosePanelVisible: false,
      editMode: 'easy',
      autoSaveTimer: null,
      // 自动保存开关，调试使用，方便代码维护，没有实际业务会修改该状态
      autoSaveEnable: true,
      rendered: false,
      preSavedData: {
        graph: '',
        productInfo: ''
      }
    }
  },
  async mounted() {
    // if (!this.$route.params.id) {
    //   this.$confirm('没有查询到场景流引擎信息，请退出重试！', '警告', {
    //     confirmButtonText: '确定',
    //     type: 'error'
    //   }).then(() => {
    //     window.history.go(-1)
    //   })
    //   return
    // }
    await this.getProductInfo(this.$route.params.id)

    this.graph = MainGraph(document.getElementById('container'))
    this.$store.commit('graph/graph', { graph: this.graph })
    this.dnd = new Addon.Dnd({
      target: this.graph,
      scaled: false,
      animation: true,
      getDragNode: (node) => node.clone({ keepId: true }),
      getDropNode: (node) => {
        return node.clone({ keepId: true })
      },
      validateNode: (node) => {
        if (this.editMode === 'stage') {
          let res = false
          const bbox = node.getBBox()
          const parentNodes = this.graph.getNodes().filter(node => node.children)
          for (let i = 0; i < parentNodes.length; i++) {
            const targetBBox = parentNodes[i].getBBox()
            if (targetBBox.containsRect(bbox)) {
              res = true
              break
            }
          }
          return res
        }
        // 并行节点不能放进并行节点
        if (node.data.additionalInfo.component === 'AdvancedLibParallelStartNode') {
          let res = true
          const bbox = node.getBBox()
          const parallelNodes = this.graph.getNodes().filter(node => node.data.additionalInfo.component === 'AdvancedLibParallelNode')
          for (let i = 0; i < parallelNodes.length; i++) {
            const targetBBox = parallelNodes[i].getBBox()
            if (targetBBox.containsRect(bbox)) {
              res = false
              break
            }
          }
          return res
        }
        return true
      }
    })
    this.createRuleChain()
    this.addRuleChainEvent()

    if (this.editEnable) {
      this.autoSave()
    }
  },
  methods: {
    /**
     * 创建规则链节点
     * 1. 递归创建规则链节点。
     * 2. 递归创建规则链连线。
     * 3. 若为stage编辑模式，则需要创建stage相关节点
     */
    createRuleChain() {
      this.graph.disableHistory()
      const res = RuleChainApi.getRuleChain(this.$route.params.id)
        if (res.data.extendData?.editMode) {
          this.editMode = res.data.extendData.editMode
        }
        // 如果没有节点（即新版本），则默认创建开始节点
        if (this.editEnable && (!res.data.nodes || !res.data.nodes.length)) {
          this.createStartNode()
          // 如果是车端产品，则默认创建前置条件节点，并创建连线
          if (this.productType === ProductTypeSimple.vehicle) {
            this.createPreconditionNode()
            res.data.connections = [{
              connectType: 'none',
              sourceNodeIndex: 0,
              sourceNodeType: NodeType.startNode,
              targetNodeIndex: 1,
              targetNodeType: NodeType.sceneNode
            }]
          }
        }
        this.createRuleChainNode(res.data)
        this.createRuleChainEdges(res.data)

        if (this.editMode === 'stage') {
          this.createRuleChainStage(res.data)
        }
        this.getRuleChainSupportVehicles()
        this.graph.enableHistory()
        // adapterNodeByRuleChain({
        //   graph: this.graph,
        //   productType: this.productType,
        //   editMode: this.editMode
        // })
        this.graph.centerContent()
        this.rendered = true
        if (!this.editEnable) {
          this.graph.freeze()
        }
    },

    /**
     * 创建开始节点
     */
    createStartNode(opt = {}) {
      const startNode = {
        id: `rule-node-${this.nextNodeId++}`,
        shape: 'cube-node',
        x: 90,
        y: 150,
        zIndex: 100,
        data: {
          name: NodeConfig[NodeType.startNode].name,
          description: NodeConfig[NodeType.startNode].description,
          type: NodeType.startNode,
          nodeClass: NodeConfig[NodeType.startNode].nodeClass,
          additionalInfo: {
            icon: 'startNode',
            clazz: 'StartNode',
            component: 'ExternalNodeStartNode',
            visible: true,
            inEnabled: false,
            outEnabled: true,
            editEnabled: false,
            removeEnabled: false,
            relationTypes: [],
            defaultConfiguration: null
          },
          configuration: null
        },
        ports: [
          {
            id: this.nextPortId++,
            group: 'out'
          }
        ]
      }
      mergeObject(startNode, opt)
      return this.graph.addNode(startNode)
    },

    /**
     * 创建前置条件节点
     */
    createPreconditionNode() {
      const preconditionNode = {
        id: `rule-node-${this.nextNodeId++}`,
        shape: 'rule-node',
        x: 220,
        y: 150,
        zIndex: 100,
        data: {
          name: '前置条件',
          description: '产品执行的前置条件',
          type: NodeType.sceneNode,
          nodeClass: 'black-node',
          additionalInfo: {
            icon: 'frontConditionNode',
            clazz: 'PreconditionNode',
            component: 'SceneLibFrontConditionNode',
            visible: true,
            inEnabled: true,
            outEnabled: true,
            editEnabled: true,
            removeEnabled: false,
            relationTypes: ['True'],
            defaultConfiguration: null
          },
          configuration: {
            setPreconditions: false,
            subSceneLogic: 'and',
            subScenes: [],
            publishEvent: 'ProductReadytoStart',
            continuousJudgment: {
              isContinuousJudge: false,
              duration: 0,
              keepStrategy: 'part',
              keepTimes: 0,
              loopJudgment: true
            }
          }
        },
        ports: [
          {
            id: this.nextPortId++,
            group: 'in'
          },
          {
            id: this.nextPortId++,
            group: 'out'
          }
        ]
      }
      return this.graph.addNode(preconditionNode)
    },

    /**
     * 循环创建规则链节点
     */
    createRuleChainNode(data) {
      if (data.nodes && data.nodes.length) {
        data.nodes.forEach(el => {
          const ports = []
          if (el.additionalInfo.inEnabled) {
            ports.push({
              id: this.nextPortId++,
              group: 'in'
            })
          }
          if (el.additionalInfo.outEnabled) {
            ports.push({
              id: this.nextPortId++,
              group: 'out'
            })
          }

          const nodeData = {
            name: el.nodeName,
            description: el.nodeDescription,
            type: el.nodeType,
            nodeClass: NodeConfig[el.nodeType].nodeClass,
            configuration: el.configuration,
            additionalInfo: el.additionalInfo
          }

          if (el.frontId) {
            nodeData.frontId = el.frontId
            const exId = el.frontId.split('-')[2] - 0
            if (exId >= this.nextNodeId) {
              this.nextNodeId = exId + 1 // 新旧ID去重
            }
          }
          if (el.nodeId) {
            nodeData.nodeId = el.nodeId
          }

          const node = {
            id: nodeData.frontId || `rule-node-${this.nextNodeId}`,
            x: Math.round(el.additionalInfo.x),
            y: Math.round(el.additionalInfo.y),
            zIndex: 100,
            data: nodeData,
            ports,
            shape: el.nodeType === NodeType.flowNode || el.nodeType === NodeType.startNode ? 'cube-node' : 'rule-node'
          }
          !nodeData.frontId && (this.nextNodeId += 1)
          this.graph.addNode(node)
        })
        // 并行节点添加父子关系
        if (data.nodes.some(item => item.component === 'AdvancedLibParallelStartNode')) {
          initParallelNode(this.graph, data, this.editMode)
        }
      }
    },

    /**
     * 循环创建节点连接线
     */
    createRuleChainEdges(data) {
      // 循环创建edges
      if (data.connections && data.connections.length) {
        const nodes = this.graph.getNodes()
        data.connections.forEach(el => {
          const sourceNodePort = nodes[el.sourceNodeIndex].getPorts().find(port => port.group === 'out')
          const targetNodePort = nodes[el.targetNodeIndex].getPorts().find(port => port.group === 'in')
          const edge = {
            source: {
              cell: nodes[el.sourceNodeIndex].id,
              port: sourceNodePort.id
            },
            target: {
              cell: nodes[el.targetNodeIndex].id,
              port: targetNodePort.id
            },
            zIndex: 10
          }
          // 如果connectType存在或者不为none
          if (!el.connectType || (el.connectType && el.connectType !== 'none')) {
            // 弹窗节点的connectType值需要由id转换为对应内容
            if (['InteractiveLibDialogNode', 'InteractiveLibVoiceNode', 'InteractiveLibDialogVoiceNode'].includes(nodes[el.sourceNodeIndex].data.additionalInfo?.component)) {
              el.connectType = nodes[el.sourceNodeIndex].data.configuration?.inputParams[0].value.buttons.find(item => item.id === el.connectType - 0).title
            }

            // 如果是前置条件节点、自动触发节点、自动退出节点、主动触发节点、主动退出节点、状态变化节点，则label为成立
            if (!['SceneLibFrontConditionNode', 'SceneLibAutoTouchNode', 'SceneLibAutoExitNode', 'SceneLibManualTouchNode', 'SceneLibManualExitNode', 'AdvancedLibStateChangeNode'].includes(nodes[el.sourceNodeIndex].data.additionalInfo.component)) {
              edge.label = LabelText[el.connectType] || el.connectType
            } else {
              edge.label = '成立'
            }
          }
          this.graph.addEdge(edge)
        })
      }
    },

    /**
     * 创建规则链阶段模式
     */
    createRuleChainStage(data) {
      const nodes = this.graph.getNodes()

      // 如果是默认创建出来的开始节点，则需要加工
      if (!data.nodes || !data.nodes.length) {
        const startNode = nodes.find(node => node.data.type === NodeType.startNode)
        startNode.data.additionalInfo.parent = StageType.startStage
      }

      const startStageNode = nodes.filter(node => node.data.additionalInfo?.parent === StageType.startStage)
      const endStageNode = nodes.filter(node => node.data.additionalInfo?.parent === StageType.endStage)
      const startParallelNode = nodes.filter(node => node.data.additionalInfo?.parallelStage === StageType.startStage)
      const endParallelNode = nodes.filter(node => node.data.additionalInfo?.parallelStage === StageType.endStage)
      const { startStage, endStage } = createStageGraph(this.graph, [...startStageNode, ...startParallelNode], [...endStageNode, ...endParallelNode])
      startStage.setChildren(startStageNode)
      endStage.setChildren(endStageNode)
      startStageNode.forEach(node => node.setParent(startStage))
      endStageNode.forEach(node => node.setParent(endStage))
    },

    /**
     * 拖拽添加新节点
     */
    addNewNode(data, $event) {
      const ports = []
      if (data.configurationDescriptor.inEnabled) {
        ports.push({
          id: (this.nextPortId++) + '',
          group: 'in'
        })
      }
      if (data.configurationDescriptor.outEnabled) {
        ports.push({
          id: (this.nextPortId++) + '',
          group: 'out'
        })
      }

      const newNodeData = {
        id: `rule-node-${this.nextNodeId++}`,
        data: {
          name: data.name,
          description: data.description,
          type: data.type,
          nodeClass: NodeConfig[data.type].nodeClass,
          configuration: data.configuration || null,
          additionalInfo: {
            clazz: data.clazz,
            icon: data.icon,
            component: data.component,
            newNode: true,
            ...data.configurationDescriptor
          }
        },
        ports,
        shape: data.type === 'FLOW_NODE' ? 'cube-node' : 'rule-node'
      }

      if (!data.configurationDescriptor.editEnabled || // 如果是不可编辑节点，且有默认值，则configuration加载默认值
        ['SceneLibSceneNode', 'ServiceLibServiceNode'].includes(data.component) // 或者是配置好的场景节点和服务节点
      ) {
        newNodeData.data.configuration = data.configurationDescriptor.defaultConfiguration
        newNodeData.data.additionalInfo.defaultConfiguration = null // 删除节点默认配置 以免默认配置中有小于号导致接口报错
      }

      if (['SceneLibManualTouchNode', 'SceneLibManualExitNode'].includes(data.component)) {
        if (data.component === 'SceneLibManualExitNode' && this.editMode === 'easy') {
          return this.$message({ type: 'error', message: '简洁模式下无法创建主动退出节点' })
        }
        // 主动触发和主动退出节点加载默认值，且在整个规则链中只能有一个
        newNodeData.data.configuration = data.configurationDescriptor.defaultConfiguration
        const nodes = this.graph.getNodes().map(item => item.data?.additionalInfo?.component)
        if (nodes.includes(data.component)) return this.$message({ type: 'error', message: `规则链中只能有一个${data.name}节点` })
      }

      if (data.component === 'SceneLibAutoExitNode' && this.editMode === 'easy') {
        return this.$message({ type: 'error', message: '简洁模式下无法创建自动退出节点' })
      }

      const newNode = this.graph.createNode(newNodeData)
      this.dnd.start(newNode, $event)
    },

    /**
     * 初始化规则链画布事件
     */
    addRuleChainEvent() {
      // 节点事件
      this.graph.on('node:dblclick', ({ node }) => {
        const nodeData = node.getData()
        if (!node.children && nodeData.additionalInfo.editEnabled) {
          this.$store.commit('graph/selectedItem', {
            type: 'node',
            data: node
          })
        }
      })
      this.graph.on('node:unselected', ({ node }) => {
        const nodeData = node.getData()
        if (!node.children && nodeData.additionalInfo?.editEnabled) {
          this.$store.commit('graph/selectedItem', {
            type: '',
            data: null
          })
        }
      })
      this.graph.on('node:removed', ({ node }) => {
        // 注意，不能使用node.children
        // 模式切换时，会先清空父节点的children，再删除父节点，所以会有BUG
        if (node.id !== StageType.startStage && node.id !== StageType.endStage) {
          this.getRuleChainSupportVehicles()

          // 如果是开始阶段的节点删除时，需要对规则链进行二次设置
          // if (this.editMode === 'easy' || node._parent?.id === StageType.startStage || node._parent?.getParent()?.id === StageType.startStage) {
          //   adapterNodeByRuleChain({
          //     graph: this.graph,
          //     productType: this.productType,
          //     editMode: this.editMode,
          //     remove: node.data?.additionalInfo?.component === 'SceneLibAutoTouchNode' // 是否删除了自动触发节点
          //   })
          // }
        }
      })
      this.graph.on('node:added', ({ node }) => {
        if (this.rendered) {
          // 如果是开始阶段的节点增加时，需要对规则链进行二次设置
          // 注意：节点刚移除时，不能保证parent字段立即为空，需增加延时
          setTimeout(() => {
            node.data.additionalInfo && (node.data.additionalInfo.newNode = undefined)
            // if (this.editMode === 'easy' || node._parent?.id === StageType.startStage || node._parent?.getParent()?.id === StageType.startStage) {
            //   adapterNodeByRuleChain({
            //     graph: this.graph,
            //     productType: this.productType,
            //     editMode: this.editMode
            //   })
            // }
            // 添加并行节点时需要创建一父两子节点
            if (node.data?.additionalInfo?.component === 'AdvancedLibParallelStartNode') {
              creatParallelNode(this.graph, node, this.nextPortId, this.nextNodeId, this.editMode)
              this.nextPortId += 3
              this.nextNodeId += 2
            }
            // 往并行节点里面加节点
            if (node.parent?.data?.additionalInfo?.component === 'AdvancedLibParallelNode') {
              node.data.additionalInfo.parallelOrder = node.parent.data.additionalInfo.parallelOrder
              if (this.editMode === 'stage') { node.data.additionalInfo.parallelStage = node.parent.parent.id } // 标记并行内节点所属阶段
            }
            if (this.editMode === 'stage') {
              if (node.data.additionalInfo?.component === 'SceneLibAutoExitNode' && (node._parent?.id === StageType.startStage || node._parent?.getParent()?.id === StageType.startStage)) {
                this.graph.removeNode(node)
                this.$message({ type: 'error', message: '自动退出节点仅在结束阶段' })
              }
              if (node.data.additionalInfo?.component === 'SceneLibManualExitNode' && (node._parent?.id === StageType.startStage || node._parent?.getParent()?.id === StageType.startStage)) {
                this.graph.removeNode(node)
                this.$message({ type: 'error', message: '主动退出节点仅在结束阶段' })
              }
              if (node.data.additionalInfo?.component === 'SceneLibManualTouchNode' && (node._parent?.id === StageType.endStage || node._parent?.getParent()?.id === StageType.endStage)) {
                this.graph.removeNode(node)
                this.$message({ type: 'error', message: '主动触发节点仅在开始阶段' })
              }
            }
            this.getRuleChainSupportVehicles()
          }, 300)
        }
      })

      // 连线事件
      this.graph.on('edge:dblclick', ({ edge }) => {
        if (edge.getLabels().length) {
          this.$store.commit('graph/selectedItem', {
            type: 'edge',
            data: edge
          })
        }
      })
      this.graph.on('edge:selected', ({ edge }) => {
        // 注意，连线的删除按钮分为两个部分定义
        // 此处定义无标签时的删除按钮
        // 有标签的删除按钮在graph.config.js中定义
        if (!edge.getLabels().length) {
          edge.addTools({
            name: 'button-remove',
            args: EdgeRemoveBtnArgs
          })
        }
        edge.attr({
          line: {
            strokeWidth: 4
          }
        })
      })
      this.graph.on('edge:unselected', ({ edge }) => {
        edge.removeTools('button-remove')
        edge.attr({
          line: {
            strokeWidth: 2
          }
        })
        if (edge.getLabels().length) {
          this.$store.commit('graph/selectedItem', {
            type: '',
            data: null
          })
        }
      })
      this.graph.on('edge:connected', ({ isNew, edge }) => {
        // 节点链接后自动添加标签
        if (isNew) {
          const sourceCell = edge.getSourceCell()
          const sourceCellData = sourceCell.getData()
          if (['SceneLibFrontConditionNode', 'SceneLibAutoTouchNode', 'SceneLibAutoExitNode', 'SceneLibManualTouchNode', 'SceneLibManualExitNode', 'AdvancedLibStateChangeNode'].includes(sourceCellData.additionalInfo.component)) {
            edge.setLabels('成立')
          } else if ([NodeType.serviceNode, NodeType.tspNode, NodeType.thirdNode].includes(sourceCellData.type)) {
            edge.setLabels('执行成功')
          } else if (['SceneLibSceneNode', 'AdvancedLibHistoricalNode'].includes(sourceCellData.additionalInfo.component)) {
            edge.setLabels('判断成功')
          } else if (sourceCellData.additionalInfo.relationTypes?.length) {
            edge.setLabels(LabelText[sourceCellData.additionalInfo?.relationTypes[0]] || sourceCellData.additionalInfo?.relationTypes[0])
          }
        }
      })
      this.graph.on('node:change:position', ({ node, options }) => {
        // 子节点移动时，自动扩展父节点
        if (options.skipParentHandler) { return }
        const children = node.getChildren()
        if (children && children.length) { node.prop('originPosition', node.getPosition()) }
        const parent = node.getParent()
        if (parent && parent.isNode()) {
          let originSize = parent.prop('originSize')
          if (originSize == null) { parent.prop('originSize', parent.getSize()) }
          originSize = parent.prop('originSize')
          let originPosition = parent.prop('originPosition')
          if (originPosition == null) { parent.prop('originPosition', parent.getPosition()) }
          originPosition = parent.prop('originPosition')
          let x = originPosition.x
          let y = originPosition.y
          let cornerX = originPosition.x + originSize.width
          let cornerY = originPosition.y + originSize.height
          let hasChange = false
          const children = parent.getChildren() || []
          children.forEach((child) => {
            const bbox = child.getBBox().inflate(30) // 边框预留padding30
            const corner = bbox.getCorner()
            if (bbox.x < x) { x = bbox.x; hasChange = true }
            if (bbox.y < y) { y = bbox.y; hasChange = true }
            if (corner.x > cornerX) { cornerX = corner.x; hasChange = true }
            if (corner.y > cornerY) { cornerY = corner.y; hasChange = true }
          })
          if (hasChange) { parent.prop({ position: { x, y }, size: { width: cornerX - x, height: cornerY - y } }, { skipParentHandler: true }) }
          if (hasChange && parent.getParent()) { // 并行区域内部节点移动，阶段节点跟随扩展
            parent.prop({ position: { y: parent.position().y - 0.01 } })
          }
          if (hasChange && ['start-stage-node', 'end-stage-node'].includes(parent.id)) {
            // 开始阶段节点下移把结束阶段往下挤
            if (parent.id === 'start-stage-node') {
              const endStageNode = this.graph.getCellById(StageType.endStage)
              const destance = cornerY - endStageNode.position().y
              endStageNode.prop({ position: { x: endStageNode.position().x, y: endStageNode.position().y + destance } })
              const endStageNodes = this.graph.getNodes().filter(node => {
                return node.getParent()?.id === StageType.endStage ||
                (node.getParent()?.data?.additionalInfo?.component === 'AdvancedLibParallelNode' &&
                node.getParent().getParent().id === StageType.endStage)
              })
              endStageNodes.forEach(item => item.prop({ position: { x: item.position().x, y: item.position().y + destance } }))
              window.stageWrapper = { x, y, width: cornerX - x, height: cornerY - y } // 重新保存阶段节点的位置信息用于限制内部节点移动范围
            }
            // 阶段左侧阶段名称自适应高度
            const parentDom = document.querySelector(`g[data-cell-id="${parent.id}"] > rect[fill="#f3f4f600"]`)
            debounce(() => { // 防抖
              const height = parentDom?.getAttribute('height')
              const rectDom = parentDom?.nextSibling
              if (height && rectDom) { rectDom.style.height = `${height}px` }
            }, 10)()
          }
        }
      })

      // history事件
      this.graph.history.on('add', () => {
        if (this.editEnable) {
          this.$store.commit('graph/setExitState', false)
        }
      })
      this.graph.history.on('undo', () => {
        if (this.canExit && this.editEnable) {
          this.$store.commit('graph/setExitState', false)
        }
      })
      this.graph.history.on('redo', () => {
        if (this.canExit && this.editEnable) {
          this.$store.commit('graph/setExitState', false)
        }
      })
    },

    /**
     * 清空画布中的已选择的节点和边
     */
    clearSelected() {
      this.graph.cleanSelection()
      this.$store.commit('graph/selectedItem', { type: '', data: null })
    },

    /**
     * 节点保存回调
     */
    saveNode(configuration, nameData = {}) {
      const data = this.selectedItem.data
      data.replaceData({ ...data.getData(), configuration, ...nameData })
      console.log('保存数据：', data.getData())
      this.clearSelected()
      this.getRuleChainSupportVehicles()
    },

    /**
     * 规则链保存按钮
     * @param {boolean} autoSave 是否自动保存
     * @param {boolean} end 校验未通过时是否继续，默认继续
     */
    async saveRuleChain(autoSave, end = false) {
      this.saveLoading = true
      formatSubmitData(this.graph) // 保存数据前对节点数据格式化

      // 如果产品未发生变更，则不进行后续保存
      const graphChange = graphIsChange(this.graph, this.productInfo, this.preSavedData)
      if (!graphChange) {
        this.saveLoading = false
        return false
      }
      const validateResult = await this.validateRuleChain(autoSave)

      if (end && !validateResult) {
        this.saveLoading = false
        return false
      }
      const nodes = this.graph.getNodes()
      const edges = this.graph.getEdges()
      const filterNodes = []

      // 保存时先清空自动保存定时器
      if (validateResult) {
        clearInterval(this.autoSaveTimer)
      }

      const ruleChainData = {
        productDef: {
          productId: this.productInfo.basicId,
          productName: this.productInfo.productName,
          productType: this.productInfo.productType,
          versionId: this.productInfo.id,
          versionName: this.productInfo.version,
          versionDescription: this.productInfo.remark,
          versionStatus: this.productInfo.productStatus,
          oemList: this.productInfo.oemList,
          ccuIdcVersionList: this.productInfo.ccuIdcVersionList,
          validityType: this.productInfo.durationType,
          validityPeriod: this.productInfo.validityPeriod,
          triggerDesc: this.productInfo.triggerDesc || '',
          serviceExecuteDesc: this.productInfo.serviceExecuteDesc || '',
          optionalSignals: this.productInfo.optionalSignals,
          voiceCommandList: [],
          // 触发方式：auto(自动)/manual(手动)/mix(自动+手动)
          executeMode: null,
          validateResult: validateResult ? 1 : 0
        },
        nodes: [],
        connections: [],
        firstNodeIndex: null,
        ruleChainId: this.productInfo.ruleChainId,
        extendData: {
          editMode: this.editMode,
          // 结束阶段开始节点索引
          phaseNodeIndex: null,
          // 是否包含服务回退
          serviceRecover: 0
        }
      }

      // 循环填充node
      nodes.forEach(node => {
        const nodeData = node.data
        // 父节点不进行填充，实际为stage节点
        if (!node.children) {
          const resData = {
            nodeName: nodeData.name,
            nodeDescription: nodeData.description,
            nodeType: nodeData.type,
            frontId: node.id,
            additionalInfo: nodeData.additionalInfo || {},
            configuration: nodeData.configuration
          }
          if (nodeData.nodeId) {
            resData.nodeId = nodeData.nodeId
          }
          // 记录当前节点所处的阶段
          if (node.parent && [StageType.startStage, StageType.endStage].includes(node.parent.id)) {
            resData.additionalInfo.parent = node.parent.id
          }
          resData.additionalInfo.x = Math.round(node.position().x)
          resData.additionalInfo.y = Math.round(node.position().y)
          filterNodes.push(node)
          ruleChainData.nodes.push(resData)
        }
      })

      // 循环填充edge
      edges.forEach(edge => {
        const sourceNode = edge.getSourceNode()
        const targetNode = edge.getTargetNode()
        const sourceIndex = filterNodes.findIndex(el => el.getPortsByGroup('out').length && el.getPortsByGroup('out')[0].id === sourceNode.getPortsByGroup('out')[0].id)
        const targetIndex = filterNodes.findIndex(el => el.getPortsByGroup('in').length && el.getPortsByGroup('in')[0].id === targetNode.getPortsByGroup('in')[0].id)
        const resData = {
          sourceNodeType: sourceNode.data.type,
          sourceNodeIndex: sourceIndex,
          targetNodeType: targetNode.data.type,
          targetNodeIndex: targetIndex,
          // 没有链接关系时，默认关系为'none'，如开始节点、结束阶段开始节点、并行开始节点等
          connectType: edge.labels.length ? (LabelText[edge.labels[0].attrs.label.text] || edge.labels[0].attrs.label.text) : 'none'
        }
        ruleChainData.connections.push(resData)
      })

      // 基于规则链数据修改最终提交数据
      editRuleChainDataByNode({
        ruleChainData: ruleChainData,
        nodes: filterNodes,
        editMode: this.editMode,
        graph: this.graph,
        productType: this.productType
      })

      const res = await RuleChainApi.saveRuleChainInfo(ruleChainData)
      this.saveLoading = false
      if (res.code === 200) {
        if (!autoSave) {
          if (validateResult) this.$message.success('保存成功')
          else this.$message.warning('保存成功，但规则链存在未解决的问题，请在问题检测中查看')
        }

        // 保存本次画布的全部数据，用于后续差异检测
        this.preSavedData = {
          graph: JSON.stringify(this.graph.toJSON()),
          productInfo: JSON.stringify(this.productInfo)
        }
        this.$store.commit('graph/setExitState', true)
      } else {
        this.$message.error(res.msg)
      }
      // 恢复定时保存功能
      if (validateResult) {
        this.autoSave()
      }
      return true
    },

    /**
     * 规则链校验，如果是自动保存则不提示信息
     */
    async validateRuleChain() {
      const clashList = []
      if (!validateNoEdgeNode(this.graph, this.editMode)) {
        clashList.push({ msg: '规则链存在孤立节点，注意检查', type: 'error', validTime: 'global' })
      }
      const emptyNode = validateEmptyNode(this.graph)
      if (!emptyNode.result) {
        emptyNode.data.forEach(node => { clashList.push({ msg: '为空白节点', node: node, type: 'error', validTime: 'global' }) })
      }
      const noTouchRes = validateNoTouchNode(this.graph, this.editMode, this.productInfo)
      if (!noTouchRes.result) {
        clashList.push({ msg: noTouchRes.msg, type: 'error', validTime: 'global' })
      }
      const surplusTouchRes = validateSurplusTouchNode(this.graph, this.productInfo)
      if (!surplusTouchRes.result) {
        clashList.push({ msg: surplusTouchRes.msg, type: 'error', validTime: 'global' })
      }
      if (!validateNoEndNode(this.graph)) {
        clashList.push({ msg: '规则链缺少结束节点，注意检查', type: 'error', validTime: 'global' })
      }
      if (!validateSupportVehicles(this.productInfo, this.supportVehicles)) {
        if (this.productInfo.oemList?.length) {
          clashList.push({ msg: '已选定的车型不适用于本产品', type: 'error', validTime: 'global' })
        } else {
          clashList.push({ msg: '产品尚未选定车型', type: 'error', validTime: 'global' })
        }
      }
      // 自动触发相似度检测
      const autoTouchRes = await validateAutoTouchNode(this.graph, this.productInfo)
      if (!autoTouchRes.result) {
        autoTouchRes.msg.forEach(item => {
          clashList.push({ msg: item, type: 'warning', validTime: 'global' })
        })
      }
      // 语音命令词检测
      const voiceRes = await validateVoiceNode(this.productInfo)
      if (!voiceRes.result) {
        clashList.push({ msg: voiceRes.msg, type: 'error', validTime: 'global' })
      }
      // 全局校验没问题时，进行有效性校验
      if (clashList.length === 0) {
        const signalAndServiceList = getRuleChainAllSignalsAndServices(this.graph)
        const availableRes = await RuleChainApi.validateRuleChainAvailable(signalAndServiceList)
        if (availableRes.msg) {
          clashList.push({ msg: availableRes.msg, type: 'error', validTime: 'global' })
        }
      }
      this.setClashList(clashList)
      await nodeClashValidate(this.graph.getNodes(), this.graph)
      return !clashList.filter(item => item.type === 'error').length
    },

    /**
     * 获取规则链支持的全部车型
     */
    getRuleChainSupportVehicles() {
      const signalAndServiceList = getRuleChainAllSignalsAndServices(this.graph, true)
        this.supportVehicles = RuleChainApi.getSupportVehicles(signalAndServiceList).data
    },

    /**
     * 阶段模式切换
     */
    editModeChange(editMode) {
      this.graph.disableHistory()
      if (editMode === 'stage') {
        const nodes = this.graph.getNodes()
        const { startStage, endStage } = createStageGraph(this.graph)
        const startStageNodes = nodes.filter(node => node.id !== StageType.startStage && node.id !== StageType.endStage)
        const endStartNode = this.createStartNode({
          x: endStage.getBBox().x + 70,
          y: endStage.getBBox().y + 70,
          data: {
            additionalInfo: {
              icon: 'endStartNode',
              clazz: 'StopPhaseStartNode',
              component: 'AdvancedLibEndStartNode',
              parent: StageType.endStage
            }
          }
        })
        startStage.setChildren(startStageNodes)
        startStageNodes.forEach(node => !node.parent && node.setParent(startStage))
        endStage.setChildren([endStartNode])
        endStartNode.setParent(endStage)
      }

      if (editMode === 'easy') {
        const startStage = this.graph.getCellById(StageType.startStage)
        if (startStage) {
          startStage.setChildren(null)
          this.graph.removeCells([StageType.startStage, StageType.endStage])
        }
      }
      // 必须保证所有节点创建或删除完毕，才可更新editMode
      this.editMode = editMode
      this.graph.enableHistory()
    },

    /**
     * 切换车型匹配面板可见性
     */
    toggleVehicleValidatePanel() {
      this.validatePanelVisible = !this.validatePanelVisible
      this.clashPanelVisible = false
      this.choosePanelVisible = false
    },

    /**
     * 切换冲突面板可见性
     */
    toggleClashValidatePanel() {
      this.clashPanelVisible = !this.clashPanelVisible
      this.validatePanelVisible = false
      this.choosePanelVisible = false
      this.clashPanelVisible && this.validateRuleChain()
    },

    /**
     * 切换选装面板可见性
     */
    toggleChooseValidatePanel() {
      this.choosePanelVisible = !this.choosePanelVisible
      this.validatePanelVisible = false
      this.clashPanelVisible = false
    },

    /**
     * 前往仿真测试系统
     */
    goSimulation() {
      // 跳转前先自动保存，若校验出错则不保存
      this.saveRuleChain(false, true).then(res => {
        if (res) {
          const simulationSystemHref = `/goSimulation/tool?productVersionId=${this.productInfo.id}`
          window.open(simulationSystemHref)
        }
      })
    },

    /**
     * 规则链退出
     * 当规则链保存时才可退出。若保存后操作历史记录（前进/后退），则不可退出
     */
    exit() {
      if (this.canExit) {
        window.history.go(-1)
      } else {
        this.$confirm('有未保存数据，继续该操作数据将丢失。是否确认?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          window.history.go(-1)
        })
      }
    },

    /**
     * 自动保存
     */
    autoSave() {
      if (this.autoSaveEnable) {
        this.autoSaveTimer = setInterval(() => {
          this.saveRuleChain(true)
        }, 3 * 1000 * 60)
      }
    },

    ...mapActions('product', ['getProductInfo']),
    ...mapActions('clash', ['setClashList'])
  },
  computed: {
    ...mapState({
      loading: state => state.product.loading,
      productType: state => state.product.type,
      productInfo: state => state.product.productInfo,
      editEnable: state => true,
      clashList: state => state.clash.clashList,
      selectedItem: state => state.graph.selectedItem,
      canExit: state => state.graph.canExit
    })
  }
}
</script>

<style lang="scss" scoped>
@import "~@/assets/styles/mixin.scss";
@import "~@/assets/styles/variables.scss";

.basic-layout {
  @include clearfix;
  position: relative;
  width: 100%;
  height: 100%;
  min-width: 1280px;
  min-height: 600px;
}
.mainLayout {
  display: flex;
  width: 100%;
  min-height: calc(600px - #{$app-header-height});
  height: calc(100vh - #{$app-header-height});
  background-color: #f2f3f5;
}
#container {
  position: relative;
  flex: 1;
}
.ruleChain-container {
  position: relative;
  display: flex;
  flex: 1;
  overflow: hidden;
  max-height: calc(100vh - #{$app-header-height});
}

</style>
